Type et méthodes

JULIA possède un système de type et de méthode qui lui confère une approche objet. La fonction typeof() renvoie le type d'une variable de base Int32, Float64... JULIA est conçu pour permettre facilement d'étendre l'environnement à de nouveaux type de variable.

Le types sont organisés suivant un hiérarchie comme on peut le voir sur l'arborescence partielle ci-dessous

(arborescence générée à l'aide de https://github.com/tanmaykm/julia_types/blob/master/julia_types.jl)

+- Any << abstract immutable size:0 >> . +- Number << abstract immutable size:0 >> . . +- Complex128 = Complex{Float64} << concrete immutable pointerfree size:16 >> . . +- Complex = Complex{Float32} << concrete immutable pointerfree size:8 >> . . +- Complex64 = Complex{Float32} << concrete immutable pointerfree size:8 >> . . +- Complex32 = Complex{Float16} << concrete immutable pointerfree size:4 >> . . +- Real << abstract immutable size:0 >> . . . +- Rational = Rational{T<:Integer} << concrete immutable size:16 >> . . . +- FloatingPoint << abstract immutable size:0 >> . . . . +- Float32 << concrete immutable pointerfree size:4 >> . . . . +- BigFloat << concrete mutable pointerfree size:32 >> . . . . +- Float64 << concrete immutable pointerfree size:8 >> . . . . +- Float16 << concrete immutable pointerfree size:2 >> . . . +- Integer << abstract immutable size:0 >> . . . . +- Signed << abstract immutable size:0 >> . . . . . +- Int8 << concrete immutable pointerfree size:1 >> . . . . . +- Int16 << concrete immutable pointerfree size:2 >> . . . . . +- Int128 << concrete immutable pointerfree size:16 >> . . . . . +- Int64 << concrete immutable pointerfree size:8 >> . . . . . +- Int = Int64 << concrete immutable pointerfree size:8 >> . . . . . +- Int32 << concrete immutable pointerfree size:4 >> . . . . +- BigInt << concrete mutable pointerfree size:16 >> . . . . +- Unsigned << abstract immutable size:0 >> . . . . . +- Uint = Uint64 << concrete immutable pointerfree size:8 >> . . . . . +- Uint8 << concrete immutable pointerfree size:1 >> . . . . . +- Uint32 << concrete immutable pointerfree size:4 >> . . . . . +- Uint16 << concrete immutable pointerfree size:2 >> . . . . . +- Uint128 << concrete immutable pointerfree size:16 >> . . . . . +- Uint64 << concrete immutable pointerfree size:8 >>

Remarquez "abstract" et "concrete" dans cette arborescence.

Méthodes

A chaque fonction est associée une méthode dépendante du type d'entrée comme dans ce qui suit suivant que l'entrée soit un entier ou pas.


In [30]:
function f(x::Any)
    sin(x+1)
end


Out[30]:
f (generic function with 2 methods)

In [31]:
function f(n::Integer)
    n
end


Out[31]:
f (generic function with 2 methods)

In [32]:
f(3.0)


Out[32]:
-0.7568024953079282

In [33]:
f(3)


Out[33]:
3

In [34]:
f(im)


Out[34]:
1.2984575814159773 + 0.6349639147847361im

In [35]:
f(-2)


Out[35]:
-2

In [37]:
+


Out[37]:
+ (generic function with 163 methods)

In [38]:
+(1,2)


Out[38]:
3

In [15]:
f(sqrt(2))


UndefVarError: gamma not defined

Stacktrace:
 [1] f(::Float64) at ./In[7]:2
 [2] top-level scope at In[15]:1

Construction d'un nouveau Type de variable

En premier lieu il faut définir un type abstrait puis une instance sous-hiérarchiquement concrète :


In [58]:
abstract type Grid end # juste en dessous de Any
mutable struct Grid1d <: Grid
    debut::Float64
    fin::Float64
    n::Int32
end


invalid redefinition of constant Grid1d

Stacktrace:
 [1] top-level scope at none:0

In [40]:
a=Grid1d(0,1,2)


Out[40]:
Grid1d(0.0, 1.0, 2)

In [41]:
a.debut


Out[41]:
0.0

In [42]:
a.fin


Out[42]:
1.0

In [43]:
a.n


Out[43]:
2

Surcharge des opérateurs

La surcharge des opérations usuelles se fait en définissant une nouvelle méthode associé au nouveau type pour chaque opérateur, commençons par surcharger l'affichage à l'écran de notre nouveau type. Pour cela on va ajouter une méthode à la fonction "show"


In [44]:
function Base.show(io::IO,g::Grid1d)
    print(io, "Grid 1d : début $(g.debut) , fin $(g.fin) , $(g.n) éléments\n")
end

In [45]:
Base.show(a)


Grid 1d : début 0.0 , fin 1.0 , 2 éléments

In [47]:
println(a)


Grid 1d : début 0.0 , fin 1.0 , 2 éléments


In [48]:
a


Out[48]:
Grid 1d : début 0.0 , fin 1.0 , 2 éléments

Addition, soustraction ...

Ces fonctions sont de la forme +(), -() c'est à dire


In [55]:
import Base:+
function +(g::Grid1d,n::Integer)
    g.n +=n
    return g
end


Out[55]:
+ (generic function with 164 methods)

In [56]:
a=Grid1d(0,1,2)


Out[56]:
Grid 1d : début 0.0 , fin 1.0 , 2 éléments

In [57]:
a+2


Out[57]:
Grid 1d : début 0.0 , fin 1.0 , 4 éléments

In [60]:
a+=1


Out[60]:
Grid 1d : début 0.0 , fin 1.0 , 5 éléments

Attention l'addition n'est pas forcément commutative !


In [61]:
2+a


MethodError: no method matching +(::Int64, ::Grid1d)
Closest candidates are:
  +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502
  +(::T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}, !Matched::T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} at int.jl:53
  +(::Union{Int16, Int32, Int64, Int8}, !Matched::BigInt) at gmp.jl:447
  ...

Stacktrace:
 [1] top-level scope at In[61]:1

ni unaire !


In [63]:
-a


MethodError: no method matching -(::Grid1d)
Closest candidates are:
  -(!Matched::Bool, !Matched::Complex{Bool}) at complex.jl:279
  -(!Matched::Bool, !Matched::Bool) at bool.jl:105
  -(!Matched::Bool) at bool.jl:102
  ...

Stacktrace:
 [1] top-level scope at In[63]:1

Notez le message d'erreur qui est très claire !


In [64]:
a+[1,2]


MethodError: no method matching +(::Grid1d, ::Array{Int64,1})
Closest candidates are:
  +(::Any, ::Any, !Matched::Any, !Matched::Any...) at operators.jl:502
  +(::Grid1d, !Matched::Integer) at In[55]:3
  +(!Matched::Array, ::Array...) at arraymath.jl:44
  ...

Stacktrace:
 [1] top-level scope at In[64]:1

Autres surcharges

Toutes les fonctions usuelles sont surchargeable sans limite : size(); det() ...


In [65]:
function Base.size(g::Grid)
    return g.n
end

In [66]:
size(a)


Out[66]:
5

In [67]:
function Base.det(g::Grid1d)
    g.fin-g.debut
end


UndefVarError: det not defined

Stacktrace:
 [1] getproperty(::Module, ::Symbol) at ./sysimg.jl:13
 [2] top-level scope at In[67]:1

In [27]:
det(a)


Out[27]:
1.0

Type et constructeurs

Chaque langage "objet" définit un constructeur pour ces objets. Nous avons déjà utiliser un constructeur générique qui rempli chaque champ du nouveau type. Il est possible de faire une variante suivant le nombre d'arguments d'entrée et de leur type


In [28]:
abstract Grid # juste en dessous de Any
type Grid1d <: Grid
    debut::Float64
    fin::Float64
    n::Int32
    
    # constructeurs par défaut sans argument
    function Grid1d()
        new(0,0,0)
    end
    
    # constructeurs par défaut avec argument
    function Grid1d(a,b,c)
        if c<=0
            error("pas possible")
        else
            new(a,b,c)
        end
    end
end


WARNING: deprecated syntax "abstract Grid" at In[28]:2.
Use "abstract type Grid end" instead.

In [29]:
b=Grid1d(0,1,-1)


pas possible

Stacktrace:
 [1] Grid1d(::Int64, ::Int64, ::Int64) at ./In[28]:15
 [2] include_string(::String, ::String) at ./loading.jl:515

Il devient possible de déterminer un constructeurs pour différentes entrées.

Il faut au préalable bien penser sa hiérarchie de type et écrire autant de fonctions constructeurs que de cas d'initialisation du nouveau type.

Les Itérateurs

Il est possible sur un type nouveau de définir un itérateur, comme ici de parcourrir les points de la grille, définissons (surchargeons) de nouvelles fonctions ou plutôt méthodes :


In [68]:
Base.start(a::Grid1d) = 1

function Base.next(a::Grid1d, state)
    if state == 1
        return (a.debut,2)
    else
        return (a.debut+(a.fin-a.debut)*(state-1)/a.n , state+1)
    end
end

Base.done(a::Grid1d, state) = state > a.n +1


UndefVarError: start not defined

Stacktrace:
 [1] getproperty(::Module, ::Symbol) at ./sysimg.jl:13
 [2] top-level scope at In[68]:1

In [31]:
a=Grid1d(0,1,10)


Out[31]:
Grid 1d : début 0.0 , fin 1.0 , 10 éléments

In [32]:
start(a)


Out[32]:
1

In [33]:
next(a,0)


Out[33]:
(-0.1, 1)

In [34]:
for i in a
    println(i)
end


0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0

Il devient possible de construire des itérateurs sur une grille 2d, 3d renvoyant les coordonnées des points de la grille... Mais on peut imaginer sur une triangulation etc...